/*
 * This source code is part of TWaver 4.0
 *
 * Serva Software PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 * Copyright 2002 - 2011 Serva Software. All rights reserved.
 */

package demo.network.topo.bus;

import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JPanel;

import twaver.DataBoxEvent;
import twaver.DataBoxListener;
import twaver.DataBoxSequenceAdapter;
import twaver.DataBoxSequenceListener;
import twaver.Element;
import twaver.Link;
import twaver.Node;
import twaver.SelectableFilter;
import twaver.ShapeNode;
import twaver.TDataBox;
import twaver.TWaverConst;
import twaver.TWaverUtil;
import twaver.network.CanvasPaintAdapter;
import twaver.network.CanvasPaintListener;
import twaver.network.InteractionEvent;
import twaver.network.InteractionListener;
import twaver.network.TNetwork;

public class BusNetwork extends JPanel {

	private TNetwork network = new TNetwork();
	private ShapeNode bus = new ShapeNode();
	private double startX = 450;
	private double startY = 150;
	private double busWidth = 500;
	private double elementGap = 50;
	private double busVerticalGap = 200;
	private double lineLenght = 90;
	private boolean invalidateFlag = false;

	private DataBoxSequenceListener dataBoxSequenceListener = new DataBoxSequenceAdapter() {
		public void hiberarchyChanged(Element element) {
			invalidateLayout();
		}
	};

	private DataBoxListener dataBoxListener = new DataBoxListener() {
		public void elementAdded(DataBoxEvent e) {
			invalidateLayout();
		}

		public void elementRemoved(DataBoxEvent e) {
			invalidateLayout();
		}

		public void elementsCleared(DataBoxEvent e) {
			invalidateLayout();
		}
	};

	private InteractionListener interation = new InteractionListener() {
		public void interactionPerformed(InteractionEvent event) {
			if (getDataBox().size() == 0) {
				return;
			}
			if (event.getType() == InteractionEvent.ELEMENT_SMOOTH_MOVED || event.getType() == InteractionEvent.ELEMENT_LEAP_MOVED) {
				if(getDataBox().getSelectionModel().contains(bus)){
					Point2D busPoint = bus.getPoint(0);
					startX = busPoint.getX();
					startY = busPoint.getY();
					invalidateLayout();
					return;
				}

				int index = getNewIndex();
				if (index == getDataBox().getChildIndex(getDataBox().getLastSelectedElement())) {
					invalidateLayout();
					return;
				}
				getDataBox().moveTo(index, getDataBox().getLastSelectedElement());
			}
		}
	};

	PropertyChangeListener PropertyChangeListener = new PropertyChangeListener() {
		public void propertyChange(PropertyChangeEvent evt) {
			if (evt.getPropertyName().equals(TWaverConst.DATABOX)) {
				Object oldValue = evt.getOldValue();
				if (oldValue != null) {
					TDataBox oldBox = (TDataBox) oldValue;
					oldBox.removeDataBoxListener(dataBoxListener);
					oldBox.removeDataBoxSequenceListener(dataBoxSequenceListener);
				}
				getDataBox().addDataBoxListener(dataBoxListener);
				getDataBox().addDataBoxSequenceListener(dataBoxSequenceListener);
				invalidateLayout();
			}
		}
	};

	CanvasPaintListener canvasPaintListener = new CanvasPaintAdapter() {
		public void beforePaint(Graphics g) {
			if (invalidateFlag) {
				invalidateFlag = false;
				
				if (getDataBox().size() == 0) {
					return;
				}
				updateBus();
				Enumeration e = getDataBox().breadthFirstEnumeration();
				int index = 0;
				Map newLocationMap = new HashMap();
				while (e.hasMoreElements()) {
					Element element = (Element) e.nextElement();
					if (element == bus) {
						continue;
					}
					if (element instanceof Node) {
						Point point = getNewLocation(index);
						newLocationMap.put(element, point);
						index++;
					}
				}
				TWaverUtil.animateCenterLocation(
						newLocationMap,
						new Runnable(){
							public void run() {
								network.adjustCanvasSize();
							}
						},
						15,
						3);
			}
		}
	};

	private TDataBox getDataBox() {
		return network.getDataBox();
	}

	private int getNewIndex() {
		Element lastSelectedElement = getDataBox().getLastSelectedElement();
		Point point = lastSelectedElement.getCenterLocation();
		Enumeration e = getDataBox().breadthFirstEnumeration();
		int index = getDataBox().getChildIndex(lastSelectedElement);
		int nodeSize = 0;
		int originalCount = 0;
		int originalIndex = 0;
		double minDistance = -1;

		int busLineSize = bus.getPoints().size() / 2;
		double yDisplacement = -1;
		if (point.y < startY) {
			yDisplacement = startY - lineLenght;
		}
		else if (point.y > (startY + (busLineSize - 1) * busVerticalGap)) {
			yDisplacement = startY + (busLineSize - 1) * busVerticalGap - lineLenght;
		}
		else {
			for (int i = 0; i < busLineSize; i++) {
				if ((point.y > (startY + i * busVerticalGap)) && (point.y < (startY + (i + 1) * busVerticalGap))) {
					yDisplacement = startY + (i + 1) * busVerticalGap - lineLenght;
					break;
				}
			}
		}
		while (e.hasMoreElements()) {
			Element element = (Element) e.nextElement();
			if (element == bus) {
				continue;
			}
			if (element instanceof Node) {
				if (element == lastSelectedElement) {
					originalIndex = originalCount;
					continue;
				}
				originalCount++;
				if ((Math.abs(element.getCenterLocation().getY() - yDisplacement)) > 1) {
					continue;
				}
				Point inode = element.getCenterLocation();
				if (nodeSize == 0) {
					minDistance = TWaverUtil.getDistance(point, inode);
					index = getDataBox().getChildIndex(element);
				} else {
					if (minDistance > TWaverUtil.getDistance(point, inode)) {
						minDistance = TWaverUtil.getDistance(point, inode);
						index = getDataBox().getChildIndex(element);
					}
				}
				nodeSize++;
			}
		}
		Point originalPoint = getNewLocation(originalIndex);
		if ((minDistance > TWaverUtil.getDistance(point, originalPoint)) && 
		                 ((Math.abs(originalPoint.getY() - yDisplacement)) < 1)) {
			minDistance = TWaverUtil.getDistance(point, originalPoint);
			index = getDataBox().getChildIndex(lastSelectedElement);
		}
		return index;
	}

	private void invalidateLayout() {
		invalidateFlag = true;
		network.getCanvas().repaint();
	}

	public BusNetwork() {
		this(new TNetwork());
	}

	public BusNetwork(TNetwork network) {
		this.network = network;
		this.network.addCanvasPaintListener(canvasPaintListener);
		this.network.addPropertyChangeListener(PropertyChangeListener);
		this.network.addInteractionListener(interation);
		this.network.addSelectableFilter(new SelectableFilter(){
			public boolean isSelectable(Element element) {
				return !(element instanceof Link);
			}
		});
		this.getDataBox().addDataBoxListener(dataBoxListener);
		this.getDataBox().addDataBoxSequenceListener(dataBoxSequenceListener);
		this.setLayout(new BorderLayout());
		this.add(this.network);
		invalidateLayout();
	}


	private Point getNewLocation(int index) {
		int rowCount = getRowCount();
		int row = index / rowCount;
		int rowIndex = index % rowCount;
		double x = 0;
		double y = 0;
		if (row % 2 == 0) {
			x = startX + elementGap + rowIndex * elementGap;
		} else {
			x = startX + busWidth - elementGap - rowIndex * elementGap;
		}
		y = startY - lineLenght + row * busVerticalGap;
		return new Point((int) x, (int) y);
	}

	public int getRowCount() {
		return Math.max(1, (int) ((busWidth - 2 * elementGap) / elementGap) + 1);
	}

	private void updateBus() {
		bus.clear();
		int size = 0;
		Enumeration e = getDataBox().breadthFirstEnumeration();
		while (e.hasMoreElements()) {
			Element element = (Element) e.nextElement();
			if (element == bus) {
				continue;
			}
			if (element instanceof Node) {
				size++;
			}
		}

		int rowCount = getRowCount();
		int rowNum = size / rowCount;
		int rowIndex = size % rowCount;
		if (rowIndex > 0) {
			rowNum++;
		}
		for (int i = 0; i < rowNum; i++) {
			if (i == 0) {
				bus.addPoint(new Point2D.Double(startX, startY + i * busVerticalGap));
				bus.addPoint(new Point2D.Double(startX + busWidth, startY + i * busVerticalGap));
			} else {
				if (i % 2 == 0) {
					bus.addPoint(new Point2D.Double(startX + busWidth, startY + i * busVerticalGap));
				} else {
					bus.addPoint(new Point2D.Double(startX, startY + i * busVerticalGap));
				}
			}
			if (i != (rowNum - 1)) {
				bus.addPoint(new Point2D.Double(startX + busWidth * (i % 2 == 0 ? 1 : 0), startY + (i + 1) * busVerticalGap));
			}
		}
	}

	public ShapeNode getBus() {
		return bus;
	}

	public void setBus(ShapeNode bus) {
		this.bus = bus;
		invalidateLayout();
	}

	public double getStartX() {
		return startX;
	}

	public void setStartX(double startX) {
		this.startX = startX;
		invalidateLayout();
	}

	public double getStartY() {
		return startY;
	}

	public void setStartY(double startY) {
		this.startY = startY;
		invalidateLayout();
	}

	public double getBusWidth() {
		return busWidth;
	}

	public void setBusWidth(double busWidth) {
		this.busWidth = busWidth;
		invalidateLayout();
	}

	public double getBusVerticalGap() {
		return busVerticalGap;
	}

	public void setBusVerticalGap(double busVerticalGap) {
		this.busVerticalGap = busVerticalGap;
		invalidateLayout();
	}

	public TNetwork getNetwork() {
		return network;
	}

	public double getElementGap() {
		return elementGap;
	}

	public void setElementGap(double elementGap) {
		this.elementGap = elementGap;
		invalidateLayout();
	}

	public double getLineLenght() {
		return lineLenght;
	}

	public void setLineLenght(double lineHeight) {
		this.lineLenght = lineHeight;
		invalidateLayout();
	}
}